﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Text.RegularExpressions;

namespace Jobping.StickyBeak.Config
{
	public class StickyBeakConfiguration : ConfigurationSection
	{
		private static StickyBeakConfiguration _config;
		private bool? isEnabled; 

		public StickyBeakConfiguration()
		{
			this.InitializeDefault();
		}

		protected override void Init()
		{
			base.Init();
		}

		protected override void InitializeDefault()
		{
			this.GetRepository = () => { return new NLogRecorder(); };

			this.IgnoreUrls = new IgnoreCollection
				(
					new List<IgnoreItem>()
					{
						new IgnoreItem("/StickyBeak", PatternType.Exact, true),
						new IgnoreItem("/favicon.ico", PatternType.Exact, true),
						new IgnoreItem("\\.(gif|css|jpg|png|js)$", PatternType.Regex, true)
					}
				);

			this.IgnoreHeaderKeys = new IgnoreCollection
				(
					new List<IgnoreItem>()
					{
						new IgnoreItem("Cookie", PatternType.Exact, true)
					}
				);

			this.IgnoreQueryStringKeys = new IgnoreCollection
				(
					new List<IgnoreItem>()
					{
						//new IgnoreItem("Cookie", PatternType.Exact, true)
					}
				);

			this.IgnoreFormKeys = new IgnoreCollection
				(
					new List<IgnoreItem>()
					{
						new IgnoreItem("Password", PatternType.Regex, true),
						new IgnoreItem("Confirm", PatternType.Regex, true)
					}
				);
		}

		public Func<ILogRepository> GetRepository { get; set; }

		[ConfigurationProperty("Enabled", IsRequired = false, DefaultValue = true )]
		public bool Enabled
		{
			get 
			{
				if (isEnabled.HasValue)
					return isEnabled.Value;

				return (bool)this["Enabled"]; 
			}
			set 
			{
				isEnabled = value;
			}
		}

		[ConfigurationProperty("IgnoreUrls")]
		public IgnoreCollection IgnoreUrls
		{
			get { return (IgnoreCollection)this["IgnoreUrls"]; }
			protected set { this["IgnoreUrls"] = value; }
		}

		[ConfigurationProperty("IgnoreFormKeys")]
		public IgnoreCollection IgnoreFormKeys
		{
			get { return (IgnoreCollection)this["IgnoreFormKeys"]; }
			protected set { this["IgnoreFormKeys"] = value; }
		}

		[ConfigurationProperty("IgnoreQueryStringKeys")]
		public IgnoreCollection IgnoreQueryStringKeys
		{
			get { return (IgnoreCollection)this["IgnoreQueryStringKeys"]; }
			protected set { this["IgnoreQueryStringKeys"] = value; }
		}

		[ConfigurationProperty("IgnoreHeaderKeys")]
		public IgnoreCollection IgnoreHeaderKeys
		{
			get { return (IgnoreCollection)this["IgnoreHeaderKeys"]; }
			protected set { this["IgnoreHeaderKeys"] = value; }
		}

		[ConfigurationProperty("IgnoreCookies")]
		public IgnoreCollection IgnoreCookies
		{
			get { return (IgnoreCollection)this["IgnoreCookies"]; }
			protected set { this["IgnoreCookies"] = value; }
		}

		[ConfigurationProperty("IgnoreSessionKeys")]
		public IgnoreCollection IgnoreSessionKeys
		{
			get { return (IgnoreCollection)this["IgnoreSessionKeys"]; }
			protected set { this["IgnoreSessionKeys"] = value; }
		}

		public bool IgnoreUrl(string input)
		{
			return Match(input, this.IgnoreUrls);
		}

		public bool IgnoreFormKey(string input)
		{
			return Match(input, this.IgnoreFormKeys);
		}

		public bool IgnoreHeaderKey(string input)
		{
			return Match(input, this.IgnoreHeaderKeys);
		}

		public bool IgnoreQueryStringKey(string input)
		{
			return Match(input, this.IgnoreQueryStringKeys);
		}

		public bool IgnoreSessionKey(string input)
		{
			return Match(input, this.IgnoreSessionKeys);
		}

		public bool IgnoreCookie(string input)
		{
			return Match(input, this.IgnoreCookies);
		}

		private static bool Match(string input, IgnoreCollection testCases)
		{
			foreach (IgnoreItem ignore in testCases)
			{
				switch (ignore.PatternType)
				{
					case PatternType.Exact:
						if (String.Compare(input, ignore.Pattern, ignore.IgnoreCase) == 0) return true;
						continue;
					case PatternType.Regex:
						RegexOptions regexOptions = ignore.IgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
						if (Regex.IsMatch(input, ignore.Pattern, regexOptions))
							return true;
						continue;
					default:
						throw new InvalidOperationException("The pattern type is not known");
				}
			}

			return false;
		}

		public static StickyBeakConfiguration Current()
		{
			if (_config != null)
				return _config;

			_config = (StickyBeakConfiguration)ConfigurationManager.GetSection("StickyBeak");

			if (_config == null)
				_config = new StickyBeakConfiguration();

			return _config;
		}
	}

	public class IgnoreCollection : ConfigurationElementCollection
	{
		public IgnoreCollection()
			: base()
		{
		}

		public IgnoreCollection(IEnumerable<IgnoreItem> defaults)
			: base()
		{
			foreach (var item in defaults)
			{
				this.BaseAdd(item);
			}
		}

		protected override ConfigurationElement CreateNewElement()
		{
			return new IgnoreItem();
		}

		protected override object GetElementKey(ConfigurationElement element)
		{
			return ((IgnoreItem)element).Pattern;
		}
	}

	public enum PatternType
	{
		Exact,
		Regex
	}

	public class IgnoreItem : ConfigurationElement
	{
		public IgnoreItem() : base() { }

		public IgnoreItem(string pattern, PatternType patternType, bool ignoreCase)
			: base()
		{
			this.Pattern = pattern;
			this.PatternType = patternType;
			this.IgnoreCase = ignoreCase;
		}

		[ConfigurationProperty("Pattern", IsKey = true, IsRequired = true)]
		public string Pattern
		{
			get { return (string)this["Pattern"]; }
			protected set { this["Pattern"] = value; }
		}

		[ConfigurationProperty("PatternType", IsRequired = false, DefaultValue = "Exact")]
		public PatternType PatternType
		{
			get { return (PatternType)this["PatternType"]; }
			protected set { this["PatternType"] = value; }
		}

		[ConfigurationProperty("IgnoreCase", IsRequired = false, DefaultValue = true)]
		public bool IgnoreCase
		{
			get { return (bool)this["IgnoreCase"]; }
			protected set { this["IgnoreCase"] = value; }
		}
	}
}
